/*
 * @Author: liaoyu1992 1519778181@qq.com
 * @Date: 2022-11-03 22:55:11
 * @LastEditors: liaoyu1992 1519778181@qq.com
 * @LastEditTime: 2022-11-09 13:43:12
 * @FilePath: \kgantt\core\helpers\barHelpers.ts
 * @Description: 甘特条工具类
 */
import { Task } from "../types/publicTypes";
import { BarTask, TaskTypeInternal } from "../types/barTask";
import { BarMoveAction } from "../types/ganttTaskActions";
export const convertToBarTasks = (
    tasks: Task[],
    dates: Date[],
    columnWidth: number,
    rowHeight: number,
    taskHeight: number,
    barCornerRadius: number,
    handleWidth: number,
    rtl: boolean,
    barProgressColor: string,
    barProgressSelectedColor: string,
    barBackgroundColor: string,
    barBackgroundSelectedColor: string,
    projectProgressColor: string,
    projectProgressSelectedColor: string,
    projectBackgroundColor: string,
    projectBackgroundSelectedColor: string,
    milestoneBackgroundColor: string,
    milestoneBackgroundSelectedColor: string
) => {
    let barTasks = tasks.map((t, i) => {
        return convertToBarTask(
            t,
            i,
            dates,
            columnWidth,
            rowHeight,
            taskHeight,
            barCornerRadius,
            handleWidth,
            rtl,
            barProgressColor,
            barProgressSelectedColor,
            barBackgroundColor,
            barBackgroundSelectedColor,
            projectProgressColor,
            projectProgressSelectedColor,
            projectBackgroundColor,
            projectBackgroundSelectedColor,
            milestoneBackgroundColor,
            milestoneBackgroundSelectedColor
        );
    });

    /** 设置依赖 */
    barTasks = barTasks.map(task => {
        const dependencies = task.dependencies || [];
        for (let j = 0; j < dependencies.length; j++) {
            const dependence = barTasks.findIndex(
                value => value.id === dependencies[j]
            );
            if (dependence !== -1) barTasks[dependence].barChildren.push(task);
        }
        return task;
    });

    return barTasks;
};

const convertToBarTask = (
    task: Task,
    index: number,
    dates: Date[],
    columnWidth: number,
    rowHeight: number,
    taskHeight: number,
    barCornerRadius: number,
    handleWidth: number,
    rtl: boolean,
    barProgressColor: string,
    barProgressSelectedColor: string,
    barBackgroundColor: string,
    barBackgroundSelectedColor: string,
    projectProgressColor: string,
    projectProgressSelectedColor: string,
    projectBackgroundColor: string,
    projectBackgroundSelectedColor: string,
    milestoneBackgroundColor: string,
    milestoneBackgroundSelectedColor: string
): BarTask => {
    let barTask: BarTask;
    switch (task.type) {
        case "milestone":
            barTask = convertToMilestone(
                task,
                index,
                dates,
                columnWidth,
                rowHeight,
                taskHeight,
                barCornerRadius,
                handleWidth,
                milestoneBackgroundColor,
                milestoneBackgroundSelectedColor
            );
            break;
        case "project":
            barTask = convertToBar(
                task,
                index,
                dates,
                columnWidth,
                rowHeight,
                taskHeight,
                barCornerRadius,
                handleWidth,
                rtl,
                projectProgressColor,
                projectProgressSelectedColor,
                projectBackgroundColor,
                projectBackgroundSelectedColor
            );
            break;
        default:
            barTask = convertToBar(
                task,
                index,
                dates,
                columnWidth,
                rowHeight,
                taskHeight,
                barCornerRadius,
                handleWidth,
                rtl,
                barProgressColor,
                barProgressSelectedColor,
                barBackgroundColor,
                barBackgroundSelectedColor
            );
            break;
    }
    return barTask;
};
const convertToBar = (
    task: Task,
    index: number,
    dates: Date[],
    columnWidth: number,
    rowHeight: number,
    taskHeight: number,
    barCornerRadius: number,
    handleWidth: number,
    rtl: boolean,
    barProgressColor: string,
    barProgressSelectedColor: string,
    barBackgroundColor: string,
    barBackgroundSelectedColor: string
): BarTask => {
    let x1: number;
    let x2: number;
    if (rtl) {
        x2 = taskXCoordinateRTL(task.start, dates, columnWidth);
        x1 = taskXCoordinateRTL(task.end, dates, columnWidth);
    } else {
        x1 = taskXCoordinate(task.start, dates, columnWidth);
        x2 = taskXCoordinate(task.end, dates, columnWidth);
    }
    let typeInternal: TaskTypeInternal = task.type;
    if (typeInternal === "task" && x2 - x1 < handleWidth * 2) {
        typeInternal = "smalltask";
        x2 = x1 + handleWidth * 2;
    }

    const [progressWidth, progressX] = progressWithByParams(
        x1,
        x2,
        task.progress,
        rtl
    );
    const y = taskYCoordinate(index, rowHeight, taskHeight);
    const hideChildren = task.type === "project" ? task.hideChildren : undefined;

    const styles = {
        backgroundColor: barBackgroundColor,
        backgroundSelectedColor: barBackgroundSelectedColor,
        progressColor: barProgressColor,
        progressSelectedColor: barProgressSelectedColor,
        ...task.styles,
    };
    return {
        ...task,
        typeInternal,
        x1,
        x2,
        y,
        index,
        progressX,
        progressWidth,
        barCornerRadius,
        handleWidth,
        hideChildren,
        height: taskHeight,
        barChildren: [],
        styles,
    };
};
const convertToMilestone = (
    task: Task,
    index: number,
    dates: Date[],
    columnWidth: number,
    rowHeight: number,
    taskHeight: number,
    barCornerRadius: number,
    handleWidth: number,
    milestoneBackgroundColor: string,
    milestoneBackgroundSelectedColor: string
): BarTask => {
    const x = taskXCoordinate(task.start, dates, columnWidth);
    const y = taskYCoordinate(index, rowHeight, taskHeight);

    const x1 = x - taskHeight * 0.5;
    const x2 = x + taskHeight * 0.5;

    const rotatedHeight = taskHeight / 1.414;
    const styles = {
        backgroundColor: milestoneBackgroundColor,
        backgroundSelectedColor: milestoneBackgroundSelectedColor,
        progressColor: "",
        progressSelectedColor: "",
        ...task.styles,
    };
    return {
        ...task,
        end: task.start,
        x1,
        x2,
        y,
        index,
        progressX: 0,
        progressWidth: 0,
        barCornerRadius,
        handleWidth,
        typeInternal: task.type,
        progress: 0,
        height: rotatedHeight,
        hideChildren: undefined,
        barChildren: [],
        styles,
    };
};
const taskXCoordinate = (xDate: Date, dates: Date[], columnWidth: number) => {
    const index = dates.findIndex(d => d.getTime() >= xDate.getTime())-1;

    const remainderMillis = xDate.getTime() - dates[index].getTime();
    const percentOfInterval =
        remainderMillis / (dates[index + 1].getTime() - dates[index].getTime());
    const x = index * columnWidth + percentOfInterval * columnWidth;
    return x;
};
const taskXCoordinateRTL = (
    xDate: Date,
    dates: Date[],
    columnWidth: number
) => {
    let x = taskXCoordinate(xDate, dates, columnWidth);
    x += columnWidth;
    return x;
};
const taskYCoordinate = (
    index: number,
    rowHeight: number,
    taskHeight: number
) => {
    const y = index * rowHeight + (rowHeight - taskHeight) / 2;
    return y;
};

export const progressWithByParams = (
    taskX1: number,
    taskX2: number,
    progress: number,
    rtl: boolean
) => {
    const progressWidth = (taskX2 - taskX1) * progress * 0.01;
    let progressX: number;
    if (rtl) {
        progressX = taskX2 - progressWidth;
    } else {
        progressX = taskX1;
    }
    return [progressWidth, progressX];
};
export const progressByProgressWidth = (
    progressWidth: number,
    barTask: BarTask
) => {
    const barWidth = barTask.x2 - barTask.x1;
    const progressPercent = Math.round((progressWidth * 100) / barWidth);
    if (progressPercent >= 100) return 100;
    else if (progressPercent <= 0) return 0;
    else return progressPercent;
};

const progressByX = (x: number, task: BarTask) => {
    if (x >= task.x2) return 100;
    else if (x <= task.x1) return 0;
    else {
        const barWidth = task.x2 - task.x1;
        const progressPercent = Math.round(((x - task.x1) * 100) / barWidth);
        return progressPercent;
    }
};
const progressByXRTL = (x: number, task: BarTask) => {
    if (x >= task.x2) return 0;
    else if (x <= task.x1) return 100;
    else {
        const barWidth = task.x2 - task.x1;
        const progressPercent = Math.round(((task.x2 - x) * 100) / barWidth);
        return progressPercent;
    }
};

export const getProgressPoint = (
    progressX: number,
    taskY: number,
    taskHeight: number
) => {
    const point = [
        progressX - 5,
        taskY + taskHeight,
        progressX + 5,
        taskY + taskHeight,
        progressX,
        taskY + taskHeight - 8.66,
    ];
    return point.join(",");
};

const startByX = (x: number, xStep: number, task: BarTask) => {
    if (x >= task.x2 - task.handleWidth * 2) {
        x = task.x2 - task.handleWidth * 2;
    }
    const steps = Math.round((x - task.x1) / xStep);
    const additionalXValue = steps * xStep;
    const newX = task.x1 + additionalXValue;
    return newX;
};

const endByX = (x: number, xStep: number, task: BarTask) => {
    if (x <= task.x1 + task.handleWidth * 2) {
        x = task.x1 + task.handleWidth * 2;
    }
    const steps = Math.round((x - task.x2) / xStep);
    const additionalXValue = steps * xStep;
    const newX = task.x2 + additionalXValue;
    return newX;
};

const moveByX = (x: number, xStep: number, task: BarTask) => {
    const steps = Math.round((x - task.x1) / xStep);
    const additionalXValue = steps * xStep;
    const newX1 = task.x1 + additionalXValue;
    const newX2 = newX1 + task.x2 - task.x1;
    return [newX1, newX2];
};

const dateByX = (
    x: number,
    taskX: number,
    taskDate: Date,
    xStep: number,
    timeStep: number
) => {
    let newDate = new Date(((x - taskX) / xStep) * timeStep + taskDate.getTime());
    newDate = new Date(
        newDate.getTime() +
        (newDate.getTimezoneOffset() - taskDate.getTimezoneOffset()) * 60000
    );
    return newDate;
};

/**
 * 在 mousemove时开始，在 mouseup 时结束
 */
export const handleTaskBySVGMouseEvent = (
    svgX: number,
    action: BarMoveAction,
    selectedTask: BarTask,
    xStep: number,
    timeStep: number,
    initEventX1Delta: number,
    rtl: boolean
): { isChanged: boolean; changedTask: BarTask } => {
    let result: { isChanged: boolean; changedTask: BarTask };
    switch (selectedTask.type) {
        case "milestone":
            result = handleTaskBySVGMouseEventForMilestone(
                svgX,
                action,
                selectedTask,
                xStep,
                timeStep,
                initEventX1Delta
            );
            break;
        default:
            result = handleTaskBySVGMouseEventForBar(
                svgX,
                action,
                selectedTask,
                xStep,
                timeStep,
                initEventX1Delta,
                rtl
            );
            break;
    }
    return result;
};

const handleTaskBySVGMouseEventForBar = (
    svgX: number,
    action: BarMoveAction,
    selectedTask: BarTask,
    xStep: number,
    timeStep: number,
    initEventX1Delta: number,
    rtl: boolean
): { isChanged: boolean; changedTask: BarTask } => {
    const changedTask: BarTask = { ...selectedTask };
    let isChanged = false;
    switch (action) {
        case "progress":
            if (rtl) {
                changedTask.progress = progressByXRTL(svgX, selectedTask);
            } else {
                changedTask.progress = progressByX(svgX, selectedTask);
            }
            isChanged = changedTask.progress !== selectedTask.progress;
            if (isChanged) {
                const [progressWidth, progressX] = progressWithByParams(
                    changedTask.x1,
                    changedTask.x2,
                    changedTask.progress,
                    rtl
                );
                changedTask.progressWidth = progressWidth;
                changedTask.progressX = progressX;
            }
            break;
        case "start": {
            const newX1 = startByX(svgX, xStep, selectedTask);
            changedTask.x1 = newX1;
            isChanged = changedTask.x1 !== selectedTask.x1;
            if (isChanged) {
                if (rtl) {
                    changedTask.end = dateByX(
                        newX1,
                        selectedTask.x1,
                        new Date(selectedTask.end),
                        xStep,
                        timeStep
                    );
                } else {
                    changedTask.start = dateByX(
                        newX1,
                        selectedTask.x1,
                        new Date(selectedTask.start),
                        xStep,
                        timeStep
                    );
                }
                const [progressWidth, progressX] = progressWithByParams(
                    changedTask.x1,
                    changedTask.x2,
                    changedTask.progress,
                    rtl
                );
                changedTask.progressWidth = progressWidth;
                changedTask.progressX = progressX;
            }
            break;
        }
        case "end": {
            const newX2 = endByX(svgX, xStep, selectedTask);
            changedTask.x2 = newX2;
            isChanged = changedTask.x2 !== selectedTask.x2;
            if (isChanged) {
                if (rtl) {
                    changedTask.start = dateByX(
                        newX2,
                        selectedTask.x2,
                        selectedTask.start,
                        xStep,
                        timeStep
                    );
                } else {
                    changedTask.end = dateByX(
                        newX2,
                        selectedTask.x2,
                        selectedTask.end,
                        xStep,
                        timeStep
                    );
                }
                const [progressWidth, progressX] = progressWithByParams(
                    changedTask.x1,
                    changedTask.x2,
                    changedTask.progress,
                    rtl
                );
                changedTask.progressWidth = progressWidth;
                changedTask.progressX = progressX;
            }
            break;
        }
        case "move": {
            const [newMoveX1, newMoveX2] = moveByX(
                svgX - initEventX1Delta,
                xStep,
                selectedTask
            );
            isChanged = newMoveX1 !== selectedTask.x1;
            if (isChanged) {
                changedTask.start = dateByX(
                    newMoveX1,
                    selectedTask.x1,
                    selectedTask.start,
                    xStep,
                    timeStep
                );
                changedTask.end = dateByX(
                    newMoveX2,
                    selectedTask.x2,
                    selectedTask.end,
                    xStep,
                    timeStep
                );
                changedTask.x1 = newMoveX1;
                changedTask.x2 = newMoveX2;
                const [progressWidth, progressX] = progressWithByParams(
                    changedTask.x1,
                    changedTask.x2,
                    changedTask.progress,
                    rtl
                );
                changedTask.progressWidth = progressWidth;
                changedTask.progressX = progressX;
            }
            break;
        }
    }
    return { isChanged, changedTask };
};

const handleTaskBySVGMouseEventForMilestone = (
    svgX: number,
    action: BarMoveAction,
    selectedTask: BarTask,
    xStep: number,
    timeStep: number,
    initEventX1Delta: number
): { isChanged: boolean; changedTask: BarTask } => {
    const changedTask: BarTask = { ...selectedTask };
    let isChanged = false;
    switch (action) {
        case "move": {
            const [newMoveX1, newMoveX2] = moveByX(
                svgX - initEventX1Delta,
                xStep,
                selectedTask
            );
            isChanged = newMoveX1 !== selectedTask.x1;
            if (isChanged) {
                changedTask.start = dateByX(
                    newMoveX1,
                    selectedTask.x1,
                    selectedTask.start,
                    xStep,
                    timeStep
                );
                changedTask.end = changedTask.start;
                changedTask.x1 = newMoveX1;
                changedTask.x2 = newMoveX2;
            }
            break;
        }
    }
    return { isChanged, changedTask };
};
